/*
 * CDDL HEADER START
 *
 * The contents of this file are subject to the terms of the
 * Common Development and Distribution License (the "License").
 * You may not use this file except in compliance with the License.
 *
 * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
 * or https://opensource.org/licenses/CDDL-1.0.
 * See the License for the specific language governing permissions
 * and limitations under the License.
 *
 * When distributing Covered Code, include this CDDL HEADER in each
 * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
 * If applicable, add the following below this CDDL HEADER, with the
 * fields enclosed by brackets "[]" replaced with your own identifying
 * information: Portions Copyright [yyyy] [name of copyright owner]
 *
 * CDDL HEADER END
 */

#include <dirent.h>
#include <dlfcn.h>
#include <errno.h>
#include <fcntl.h>
#include <libgen.h>
#include <libintl.h>
#include <stdio.h>
#include <stdlib.h>
#include <strings.h>
#include <unistd.h>
#include <zone.h>
#include <sys/mntent.h>
#include <sys/mount.h>
#include <sys/stat.h>
#include <sys/statvfs.h>
#include <sys/dsl_crypt.h>

#include <libzfs.h>

#include "libzfs_impl.h"

#include <libshare.h>
#include <sys/systeminfo.h>

#define	MAXISALEN	257	/* based on sysinfo(2) man page */

static const char *const proto_names[SA_PROTOCOL_COUNT] = {
	[SA_PROTOCOL_NFS] = "nfs",
	[SA_PROTOCOL_SMB] = "smb",
};

int
do_mount(zfs_handle_t *zhp, const char *mntpt, const char *opts, int flags)
{
	if (mount(zfs_get_name(zhp), mntpt, MS_OPTIONSTR | flags,
	    MNTTYPE_ZFS, NULL, 0, opts, MAXISALEN) != 0)
		return (errno);
	return (0);
}

int
do_unmount(zfs_handle_t *zhp, const char *mntpt, int flags)
{
	(void) zhp;

	if (umount2(mntpt, flags) != 0)
		return (errno);
	return (0);
}

/*
 * Search the sharetab for the given mountpoint and protocol.
 */
static boolean_t
_sa_is_shared(libzfs_handle_t *hdl, const char *mountpoint, const char *proto)
{
	char buf[MAXPATHLEN], *tab;
	char *ptr;

	if (hdl->libzfs_sharetab == NULL)
		return (B_FALSE);

	(void) fseek(hdl->libzfs_sharetab, 0, SEEK_SET);

	while (fgets(buf, sizeof (buf), hdl->libzfs_sharetab) != NULL) {

		/* the mountpoint is the first entry on each line */
		if ((tab = strchr(buf, '\t')) == NULL)
			continue;

		*tab = '\0';
		if (strcmp(buf, mountpoint) == 0) {
			/*
			 * the protocol field is the third field
			 * skip over second field
			 */
			ptr = ++tab;
			if ((tab = strchr(ptr, '\t')) == NULL)
				continue;
			ptr = ++tab;
			if ((tab = strchr(ptr, '\t')) == NULL)
				continue;
			*tab = '\0';
			if (strcmp(ptr, proto) == 0) {
				return (B_TRUE);
			}
		}
	}

	return (B_FALSE);
}

boolean_t
sa_is_shared(libzfs_handle_t *hdl, const char *mountpoint, const enum sa_protocol proto)
{
	return (_sa_is_shared(hdl, mountpoint, proto_names[proto]));
}

/*
 * Make sure things will work if libshare isn't installed by using
 * wrapper functions that check to see that the pointers to functions
 * initialized in _zfs_init_libshare() are actually present.
 */

static sa_handle_t (*_sa_init)(int);
static sa_handle_t (*_sa_init_arg)(int, void *);
static int (*_sa_service)(sa_handle_t);
static void (*_sa_fini)(sa_handle_t);
static sa_share_t (*_sa_find_share)(sa_handle_t, char *);
static int (*_sa_enable_share)(sa_share_t, char *);
static int (*_sa_disable_share)(sa_share_t, char *);
static char *(*_sa_errorstr)(int);
static int (*_sa_parse_legacy_options)(sa_group_t, char *, char *);
static boolean_t (*_sa_needs_refresh)(sa_handle_t *);
static libzfs_handle_t *(*_sa_get_zfs_handle)(sa_handle_t);
static int (*_sa_zfs_process_share)(sa_handle_t, sa_group_t, sa_share_t,
    char *, char *, zprop_source_t, char *, char *, char *);
static void (*_sa_update_sharetab_ts)(sa_handle_t);

/*
 * sa_parse_legacy_options2(options, proto)
 *
 * protocol specific interfaces
 */

static int
sa_parse_legacy_options2(const char *options, const char *proto)
{
	if (_sa_parse_legacy_options != NULL) {
		return (_sa_parse_legacy_options(NULL, (char *)options,
		    (char *)proto));
	}
	return (SA_CONFIG_ERR);
}

/*
 * sa_validate_shareopts(options, proto)
 *
 * Call the legacy parse interface to get the protocol specific
 * options using the NULL arg to indicate that this is a "parse" only.
 */
int
sa_validate_shareopts(const char *options, const enum sa_protocol proto)
{
	return (sa_parse_legacy_options2(options, proto_names[proto]));
}

/*
 * zfs_sa_find_share(handle, path)
 *
 * wrapper around sa_find_share to find a share path in the
 * configuration.
 */
static sa_share_t
zfs_sa_find_share(sa_handle_t handle, char *path)
{
	if (_sa_find_share != NULL)
		return (_sa_find_share(handle, path));
	return (NULL);
}

/*
 * zfs_sa_enable_share(share, proto)
 *
 * Wrapper for sa_enable_share which enables a share for a specified
 * protocol.
 */
static int
zfs_sa_enable_share(sa_share_t share, const char *proto)
{
	if (_sa_enable_share != NULL)
		return (_sa_enable_share(share, (char *)proto));
	return (SA_CONFIG_ERR);
}

/*
 * zfs_sa_disable_share(share, proto)
 *
 * Wrapper for sa_enable_share which disables a share for a specified
 * protocol.
 */
static int
zfs_sa_disable_share(sa_share_t share, const char *proto)
{
	if (_sa_disable_share != NULL)
		return (_sa_disable_share(share, (char *)proto));
	return (SA_CONFIG_ERR);
}

/* share control */
int
sa_enable_share2(zfs_handle_t *zhp, const char *mountpoint,
    const char *shareopts, const enum sa_protocol proto,
    zprop_source_t sourcetype,  const char *sourcestr)
{
	sa_share_t share;
	libzfs_handle_t *hdl = zhp->zfs_hdl;
	int service = SA_INIT_ONE_SHARE_FROM_HANDLE;
	int ret;

	/*
	 * Function may be called in a loop from higher up stack, with libshare
	 * initialized for multiple shares (SA_INIT_SHARE_API_SELECTIVE).
	 * zfs_init_libshare_arg will refresh the handle's cache if necessary.
	 * In this case we do not want to switch to per share initialization.
	 * Specify SA_INIT_SHARE_API to do full refresh, if refresh required.
	 */
	if ((hdl->libzfs_sharehdl != NULL) && (_sa_service != NULL) &&
	    (_sa_service(hdl->libzfs_sharehdl) ==
	    SA_INIT_SHARE_API_SELECTIVE)) {
		service = SA_INIT_SHARE_API;
	}

	ret = zfs_init_libshare_arg(hdl, service, zhp);
	if (ret != SA_OK) {
		return (ret);
	}

	share = zfs_sa_find_share(hdl->libzfs_sharehdl, (char *)mountpoint);
	if (share == NULL) {
		/*
		 * This may be a new file system that was just
		 * created so isn't in the internal cache
		 * (second time through). Rather than
		 * reloading the entire configuration, we can
		 * assume ZFS has done the checking and it is
		 * safe to add this to the internal
		 * configuration.
		 */
		ret = _sa_zfs_process_share(hdl->libzfs_sharehdl,
		    NULL, NULL, (char *)mountpoint,
		    (char *)proto_names[proto], sourcetype, (char *)shareopts,
		    (char *)sourcestr, zhp->zfs_name);
		if (ret != SA_OK) {
			return (ret);
		}
		share = zfs_sa_find_share(hdl->libzfs_sharehdl,
		    (char *)mountpoint);
	}
	if (share == NULL) {
		return (SA_INVALID_PROTOCOL);
	}

	return (zfs_sa_enable_share(share, proto_names[proto]));
}

int
sa_disable_share2(libzfs_handle_t *hdl, const char *name,
    const char *mountpoint, const enum sa_protocol proto)
{
	sa_share_t share;
	int err;
	char *mntpt;
	int service = SA_INIT_ONE_SHARE_FROM_NAME;

	/*
	 * Mountpoint could get trashed if libshare calls getmntany
	 * which it does during API initialization, so strdup the
	 * value.
	 */
	mntpt = zfs_strdup(hdl, mountpoint);

	/*
	 * Function may be called in a loop from higher up stack, with libshare
	 * initialized for multiple shares (SA_INIT_SHARE_API_SELECTIVE).
	 * zfs_init_libshare_arg will refresh the handle's cache if necessary.
	 * In this case we do not want to switch to per share initialization.
	 * Specify SA_INIT_SHARE_API to do full refresh, if refresh required.
	 */
	if ((hdl->libzfs_sharehdl != NULL) && (_sa_service != NULL) &&
	    (_sa_service(hdl->libzfs_sharehdl) ==
	    SA_INIT_SHARE_API_SELECTIVE)) {
		service = SA_INIT_SHARE_API;
	}

	err = zfs_init_libshare_arg(hdl, service, (void *)name);
	if (err != SA_OK) {
		free(mntpt);	/* don't need the copy anymore */
		return (err);
	}

	share = zfs_sa_find_share(hdl->libzfs_sharehdl, mntpt);
	free(mntpt);	/* don't need the copy anymore */

	if (share == NULL) {
		return (SA_INVALID_PROTOCOL);
	}

	return (zfs_sa_disable_share(share, proto_names[proto]));
}

char *
sa_errorstr2(int err)
{
	static char errstr[32];

	if (_sa_errorstr == NULL) {
		(void) snprintf(errstr, sizeof (errstr),
		    dgettext(TEXT_DOMAIN, "unknown %d"), err);
		return (errstr);
	}

	return (_sa_errorstr(err));
}

/*
 * _zfs_init_libshare()
 *
 * Find the libshare.so.1 entry points that we use here and save the
 * values to be used later. This is triggered by the runtime loader.
 * Make sure the correct ISA version is loaded.
 */

#pragma init(_zfs_init_libshare)
static void
_zfs_init_libshare(void)
{
	void *libshare;
	char path[MAXPATHLEN];
	char isa[MAXISALEN];

#if defined(_LP64)
	if (sysinfo(SI_ARCHITECTURE_64, isa, MAXISALEN) == -1)
		isa[0] = '\0';
#else
	isa[0] = '\0';
#endif
	(void) snprintf(path, MAXPATHLEN,
	    "/usr/lib/%s/libshare.so.1", isa);

	if ((libshare = dlopen(path, RTLD_LAZY | RTLD_GLOBAL)) != NULL) {
		_sa_init = (sa_handle_t (*)(int))dlsym(libshare, "sa_init");
		_sa_init_arg = (sa_handle_t (*)(int, void *))dlsym(libshare,
		    "sa_init_arg");
		_sa_fini = (void (*)(sa_handle_t))dlsym(libshare, "sa_fini");
		_sa_service = (int (*)(sa_handle_t))dlsym(libshare,
		    "sa_service");
		_sa_find_share = (sa_share_t (*)(sa_handle_t, char *))
		    dlsym(libshare, "sa_find_share");
		_sa_enable_share = (int (*)(sa_share_t, char *))dlsym(libshare,
		    "sa_enable_share");
		_sa_disable_share = (int (*)(sa_share_t, char *))dlsym(libshare,
		    "sa_disable_share");
		_sa_errorstr = (char *(*)(int))dlsym(libshare, "sa_errorstr");
		_sa_parse_legacy_options = (int (*)(sa_group_t, char *, char *))
		    dlsym(libshare, "sa_parse_legacy_options");
		_sa_needs_refresh = (boolean_t (*)(sa_handle_t *))
		    dlsym(libshare, "sa_needs_refresh");
		_sa_get_zfs_handle = (libzfs_handle_t *(*)(sa_handle_t))
		    dlsym(libshare, "sa_get_zfs_handle");
		_sa_zfs_process_share = (int (*)(sa_handle_t, sa_group_t,
		    sa_share_t, char *, char *, zprop_source_t, char *,
		    char *, char *))dlsym(libshare, "sa_zfs_process_share");
		_sa_update_sharetab_ts = (void (*)(sa_handle_t))
		    dlsym(libshare, "sa_update_sharetab_ts");
		if (_sa_init == NULL || _sa_init_arg == NULL ||
		    _sa_fini == NULL || _sa_find_share == NULL ||
		    _sa_enable_share == NULL || _sa_disable_share == NULL ||
		    _sa_errorstr == NULL || _sa_parse_legacy_options == NULL ||
		    _sa_needs_refresh == NULL || _sa_get_zfs_handle == NULL ||
		    _sa_zfs_process_share == NULL || _sa_service == NULL ||
		    _sa_update_sharetab_ts == NULL) {
			_sa_init = NULL;
			_sa_init_arg = NULL;
			_sa_service = NULL;
			_sa_fini = NULL;
			_sa_find_share = NULL;
			_sa_disable_share = NULL;
			_sa_enable_share = NULL;
			_sa_errorstr = NULL;
			_sa_parse_legacy_options = NULL;
			(void) dlclose(libshare);
			_sa_needs_refresh = NULL;
			_sa_get_zfs_handle = NULL;
			_sa_zfs_process_share = NULL;
			_sa_update_sharetab_ts = NULL;
		}
	}
}

/*
 * zfs_init_libshare(zhandle, service)
 *
 * Initialize the libshare API if it hasn't already been initialized.
 * In all cases it returns 0 if it succeeded and an error if not. The
 * service value is which part(s) of the API to initialize and is a
 * direct map to the libshare sa_init(service) interface.
 */
static int
zfs_init_libshare_impl(libzfs_handle_t *zhandle, int service, void *arg)
{
	/*
	 * libshare is either not installed or we're in a branded zone. The
	 * rest of the wrapper functions around the libshare calls already
	 * handle NULL function pointers, but we don't want the callers of
	 * zfs_init_libshare() to fail prematurely if libshare is not available.
	 */
	if (_sa_init == NULL)
		return (SA_OK);

	/*
	 * Attempt to refresh libshare. This is necessary if there was a cache
	 * miss for a new ZFS dataset that was just created, or if state of the
	 * sharetab file has changed since libshare was last initialized. We
	 * want to make sure so check timestamps to see if a different process
	 * has updated any of the configuration. If there was some non-ZFS
	 * change, we need to re-initialize the internal cache.
	 */
	if (_sa_needs_refresh != NULL &&
	    _sa_needs_refresh(zhandle->libzfs_sharehdl)) {
		zfs_uninit_libshare(zhandle);
		zhandle->libzfs_sharehdl = _sa_init_arg(service, arg);
	}

	if (zhandle && zhandle->libzfs_sharehdl == NULL) {
		zhandle->libzfs_sharehdl = _sa_init_arg(service, arg);

		if (zhandle->libzfs_sharehdl == NULL)
			return (SA_NO_MEMORY);
	}

	return (SA_OK);
}

int
zfs_init_libshare(libzfs_handle_t *zhandle, int service)
{
	return (zfs_init_libshare_impl(zhandle, service, NULL));
}

int
zfs_init_libshare_arg(libzfs_handle_t *zhandle, int service, void *arg)
{
	return (zfs_init_libshare_impl(zhandle, service, arg));
}

/*
 * zfs_uninit_libshare(zhandle)
 *
 * Uninitialize the libshare API if it hasn't already been
 * uninitialized. It is OK to call multiple times.
 */
void
zfs_uninit_libshare(libzfs_handle_t *zhandle)
{
	if (zhandle != NULL && zhandle->libzfs_sharehdl != NULL) {
		if (_sa_fini != NULL)
			_sa_fini(zhandle->libzfs_sharehdl);
		zhandle->libzfs_sharehdl = NULL;
	}
}
